﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Diagnostics;

namespace SegGen
{
  public  class Individual
    {
        

      // the ID 
        private int m_individualID;

        public int IndividualID
        {
            get { return m_individualID; }
            set { m_individualID = value; }
        }

        
         // Reference to  gene
         private String m_gene;
         // Aggregation = Cx + 6.04 * Dx; 
         private double m_Agg;

         public double Agg
         {
             get { return m_Agg; }
             set { m_Agg = value; }
         }

       
      

        // Each individual gets a probability of selection equal
        // to their percentage of contribution in this sum.
         private double m_ProbabilityOfSelection;

         public double ProbabilityOfSelection
         {
             get { return m_ProbabilityOfSelection; }
             set { m_ProbabilityOfSelection = value; }
         }
         private double alpha;
          // H - hardness   
         

public double Alpha
{
  get { return alpha; }
  set { alpha = value; }
}
      private double m_H;

         public double H
         {
             get { return m_H; }
             set { m_H = value; }
         }
          // F = fitness value
         private double m_F;

         public double F
         {
             get { return m_F; }
             set { m_F = value; }
         }  

         // C - InternalCohesion
         private double m_C;

         public double C
         {
             get { return m_C; }
             set { m_C = value; }
         }
         // D - DissimilarityBetweenAdjacentSegments
         private double m_D;

         public double D
         {
             get { return m_D; }
             set { m_D = value; }
         }


         public String Gene
         {
             get { return m_gene; }
             set { m_gene = value; }
         }
     

         // this list contains list of segments
         private List<Segment> m_individualSegmentList = new List<Segment>();

         internal List<Segment> IndividualSegmentList
         {
             get { return m_individualSegmentList; }
             set { m_individualSegmentList = value; }
         }

        

        // generate random numbers
         public static Random m_rand = new Random((int)DateTime.Now.Ticks);

        // minimal segment size
         private int m_minSegmentSize;

        // individual size
         private int m_individualSize;

        // private double m_individualFitness;

         private Codon m_codon;

         internal Codon Codon
         {
             get { return m_codon; }
             set { m_codon = value; }
         }

      

         

        
    

       

            //public double IndividualFitness
            //{
            //    get { return m_individualFitness; }
            //    set { m_individualFitness = value; }
            //}


         public int IndividualSize
         {
             get { return m_individualSize; }
             set { m_individualSize = value; }
         }

         public int MinSegmentSize
         {
             get { return m_minSegmentSize; }
             set { m_minSegmentSize = value; }
         }

        // every byte in bitarray is 3 bytes in gene: 0,0 - AAA GGG
         public Individual(ref String _gene, int _numBoundaries, int _minSegmentSize, int numNucleotidesInCodon, int _individualID,double _alpha)
        {
            if (_individualID < 0) throw new ArgumentException("The individual ID cannot be less then 0.");
            
             m_individualID = _individualID;
            m_gene = _gene;
           // every move is like move of 3 nucleotides
             m_individualSize = _gene.Length / numNucleotidesInCodon;
         

            m_minSegmentSize = _minSegmentSize;
            m_codon = new Codon(numNucleotidesInCodon);
            alpha = _alpha;
             // add first segment to segment list, 
             // the start index of segment will be 0 and end index of segment will be size of individual
//            Segment firstSegment = new Segment(0, m_individualSize,_minSegmentSize);
             // add first segment to list, key is 0, value is firstSegment
//             m_individualSegmentList.Add( firstSegment);

            // the random number of boundaries in individual
              int randNumBoundaries = m_rand.Next(_numBoundaries);
             //int randNumBoundaries = 3; //_numBoundaries;
              int boundaryIndex = 0;

             //  if( !isPossibleToMakeRequestedNumBoundaries( randNumBoundaries) )
                   //throw new NumBoundariesException("Cannot be created this number of boudaries with this min segment size");

            // TODO: if there is a problem to add boundary in this place, should be 
            // added in another one
            // int[] testIndexArr= new int[]{12,20,30}; // 
             

              //Stopwatch swCreateRandBound = new Stopwatch();
              //swCreateRandBound.Start();
             createRandBoundaries(_numBoundaries);

              //Console.WriteLine("createRandBoundaries Time: {0} ms", swCreateRandBound.ElapsedMilliseconds);
              //swCreateRandBound.Stop();

              Stopwatch swrandNumBoundaries = new Stopwatch();
              swrandNumBoundaries.Start();
              /**********************************************************************/
              //for (int j = 0; j < _numBoundaries; j++)
              //{
              //    if (m_individualSize > MinSegmentSize * 2)
              //        boundaryIndex = m_rand.Next(MinSegmentSize, m_individualSize - MinSegmentSize);
              //    else
              //        boundaryIndex = m_rand.Next(m_individualSize);

              //    //          Console.WriteLine("Index " + boundaryIndex);
              //    // boundaryIndex = testIndexArr[j];
              //    // check if boundary in minimal range og segment size

              //    if (isSucceedAddedSegment(boundaryIndex))
              //    {
              //    }
              //    //TODO: else - if not in range

              //}// end for rand boundaries
             /**********************************************************************/


           //    Console.WriteLine("Added all boudaries to Individual Time: {0} ms", swrandNumBoundaries.ElapsedMilliseconds);
               swrandNumBoundaries.Stop();
            //TODO: calculate fitness function of individual

               Stopwatch swCalcAgg = new Stopwatch();
               swCalcAgg.Start();
               calculateAggregation();
         //      Console.WriteLine("Calc Agg Time: {0} ms", swCalcAgg.ElapsedMilliseconds);
               swCalcAgg.Stop();
             
        }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="_numBoundaries"></param>
         private void createRandBoundaries(int maxNumBoundaries)
         {
             int codonSize = m_codon.CodonNumNucleotides;
             long geneSize = m_gene.Length;
             int geneOfCodons = (int) geneSize / codonSize;
             int geneOfSegments = geneOfCodons / MinSegmentSize;

             // array of flags of boundaries
             int[] boundFlags = new int[geneOfCodons];
             // array of boundaries
             int[] boundArr = new int[geneOfSegments];
             int boundArrInd = 0;
             int randBoundIndex = 0;
             // generate boundaries
             for (int i = 0; i < maxNumBoundaries; i++ )
             {
                 randBoundIndex = m_rand.Next(m_individualSize);
                 // int[] a = {4,7,10,12};
                 //randBoundIndex = a[i];
                if (isLegalBoundary(randBoundIndex, boundFlags, MinSegmentSize, codonSize) == true)
                {
                    // set boundary flag
                    boundFlags[randBoundIndex] = 1;
                    boundArr[boundArrInd++] = randBoundIndex ;
                }
             }
             // add last segment

             if (isLegalBoundary(m_individualSize-1, boundFlags, MinSegmentSize, codonSize) == true)
             {
                 // set boundary flag
                 boundFlags[m_individualSize-1] = 1;
                 boundArr[boundArrInd++] = m_individualSize;
             }

                 // sort array of boundaries
                 Array.Sort(boundArr);
                // create and add segments to list of individual
                addSegments(boundArr);

             

         }

         private void addSegments(int[] boundArr)
         {
             int segStartInd = 0;
            
             for (int segInd = 0; segInd < boundArr.Count(); segInd++ )
             {
                 if (boundArr[segInd] != 0)
                 {
                     Segment addSegment = new Segment(segStartInd, boundArr[segInd], m_minSegmentSize);
                     addSegment.IsChanged = true;
                     m_individualSegmentList.Add(addSegment);
                     segStartInd = boundArr[segInd];
                 }
             }
            
         }

         private bool isLegalBoundary(int randBoundIndex, int[] boundFlags, int MinSegmentSize, int codonSize)
         {
             int candidateToBoundary = randBoundIndex ;
             int geneOfCodons = m_gene.Length / codonSize;


             try
             {

                 if (candidateToBoundary / MinSegmentSize == 0) return false;
                 //else if ( (geneSizeDivMinSegSize - candidateToBoundary) / MinSegmentSize / codonSize == 0) return false;


                 // check left side boundary
                 for (int lInd = 0; lInd <= MinSegmentSize; lInd++)
                 {
                     if (candidateToBoundary - lInd < 0) return false;
                     else if (boundFlags[candidateToBoundary - lInd] != 0) return false;

                 }

                 // check right side of boundary
                 for (int rInd = 0; rInd <= MinSegmentSize; rInd++)
                 {
                     if (candidateToBoundary + rInd >= geneOfCodons) return true;
                     else if (boundFlags[candidateToBoundary + rInd] != 0) return false;

                 }
             }
             catch (Exception)
             {

                 System.Console.WriteLine("create boundary error");
             }
            
             return true;
         }

        

         public void calculateAggregation()
         {
         //    System.Console.WriteLine("calculateAggregation");
             Stopwatch swInternalCohesion = new Stopwatch();
             swInternalCohesion.Start();
             m_C = InternalCohesion();
       //      Console.WriteLine("InternalCohesion Time: {0} ms", swInternalCohesion.ElapsedMilliseconds);
             swInternalCohesion.Stop();

             Stopwatch swDissimilarity = new Stopwatch();
             swInternalCohesion.Start();
             m_D = DissimilarityBetweenAdjacentSegments();
         //    Console.WriteLine("Dissimilaruty Time: {0} ms", swDissimilarity.ElapsedMilliseconds);
             swDissimilarity.Stop();
             
             m_Agg = m_C + (alpha * m_D); //6.04
         }

       

        // checking if can be added boundary with regars to min segment size
        private bool isSucceedAddedSegment(int probablyBoundaryIndex)
        {
           // System.Console.WriteLine("isSucceedAddedSegment");
            // count - Gets the number of key/value pairs contained in the SortedList.
            //if (m_individualSegmentList.Count == 0)
            //{
            //    if (probablyBoundaryIndex - m_minSegmentSize < 0)
            //    return false;
            //}
            // go through all array of boundaries and find boundary with index 
            // which more than  new probably boundary, check if 
            // size of previous boundary under new boundary is more than min segment size,
            // and size from new probably boundary under right boundary more than min seg size.
            // array example: 1, 12, 17, 28  - add 23 between 17 and 28  
            int i=0;
            Segment curSegment = null;
            for( i = 0; i < m_individualSegmentList.Count; i++)
            {
                curSegment = m_individualSegmentList.ElementAt(i);

                

                if (curSegment.SegmentStartIndex < probablyBoundaryIndex &&
                    curSegment.SegmentEndIndex > probablyBoundaryIndex)
                {
                    if (probablyBoundaryIndex - curSegment.SegmentStartIndex >= m_minSegmentSize &&
                        curSegment.SegmentEndIndex - probablyBoundaryIndex >= m_minSegmentSize)
                    {
                        // update end of first end segment
                        int rememberEndIndexOfSourceSeg = curSegment.SegmentEndIndex;
                        curSegment.SegmentEndIndex = probablyBoundaryIndex;
                        curSegment.IsChanged = true;
                        // recalculate changed size of segment
                         curSegment.SegmentSize = probablyBoundaryIndex - curSegment.SegmentStartIndex;
                        Segment addSegment = new Segment(probablyBoundaryIndex, rememberEndIndexOfSourceSeg, m_minSegmentSize);

                        m_individualSegmentList.Add( addSegment);
                        return true;
                    }
                }
            }

            //array: 1, 12, 13, 17, 28   add 30    
            //if (m_individualSegmentList.Count != 0 && probablyBoundaryIndex - m_individualSegmentList.Last().Value < m_minSegmentSize)
            //    return false;
          
            return false;
        }

        // in this function will be checked if it real to create requested number of boundaries
        private bool isPossibleToMakeRequestedNumBoundaries( int numBoundaries)
        {
            // the requested number of boundaries is much more 
            // than can be created with this minimal segment size
            if ( m_individualSize / m_minSegmentSize < numBoundaries) return false;

            return true;
        }


        //public void Mutate()
        //{
        //    // in mutation index will be mutation
        //  //  int mutationIndex = m_rand.Next((int)m_individualBoundaryArray.Length);


        //}

        public void Print()
        {

            try
            {
                Console.WriteLine( "--------------------------------------------------------- ID -> "+IndividualID+"----------");
                // print only indexes
                for (int i = 0; i < m_individualSegmentList.Count; i++)
                {
                    
                    Console.Write("Segment " + i + " : [" + m_individualSegmentList.ElementAt(i).SegmentStartIndex + " - " + m_individualSegmentList.ElementAt(i).SegmentEndIndex + " ] " + "Seg Size: " + m_individualSegmentList.ElementAt(i).SegmentSize + "   ");

                    for (int geneInd = m_individualSegmentList.ElementAt(i).SegmentStartIndex * Codon.CodonNumNucleotides; 
                         geneInd < m_individualSegmentList.ElementAt(i).SegmentEndIndex * Codon.CodonNumNucleotides; 
                         geneInd++)
                    {
                        Console.Write(Gene[geneInd]);
                    }

                    Console.WriteLine();
                }

            }
            catch (Exception ex)
            {

                Console.WriteLine("individual print error: " + ex.Message +"\n" + ex.StackTrace);
            }

            // only for test
            Console.WriteLine("Cx = :  " + m_C);
            Console.WriteLine("Dx = :  " + m_D);
            Console.WriteLine("F = :  " + m_F);
            Console.WriteLine("Agg = :  " + m_Agg);
           
        }

        // The similarity sim( t1 , t2 ) between two triplets according to indexes of triplets
        public double Sim(int triplet1_Index, int triplet2_Index)
        {
            // the similaruty of triplets
            double simTriplets = 0;
            // 
            try
            {
                for (int i = 0; i < m_codon.CodonNumNucleotides; i++)
                {
                    if (m_gene[triplet1_Index * m_codon.CodonNumNucleotides + i].Equals(m_gene[triplet2_Index * m_codon.CodonNumNucleotides + i]))
                    {
                        //simTriplets += 0.3333;
                        simTriplets += ((double)1 / m_codon.CodonNumNucleotides);
                    }
                }
            }
            catch (Exception)
            {

                System.Console.WriteLine("sim error");
                throw new ArgumentOutOfRangeException("sim error");
            }

            return simTriplets;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public double InternalCohesion()
        {
            //System.Console.WriteLine("InternalCohesion");

            int startTripletIndex1 = 0;
            double NbCouples = 0; // NbCouples - The number of possible couples of triplets in seg
            // int endSegmentIndex = 0;
            double tripletSimilarityInSegment = 0;
            double averageTripletSimilarityInSegment = 0;


            // for all segments in individual make internal cohesion of triplets
            for (int numSegment = 0; numSegment < m_individualSegmentList.Count; numSegment++)
            {
                Segment curSegment = m_individualSegmentList.ElementAt(numSegment);

                // if segment has been changed then recalculate
         //       if (curSegment.IsChanged == true)
                {

                    try
                    {
                        for (int tripletIndex1 = curSegment.SegmentStartIndex; tripletIndex1 < curSegment.SegmentEndIndex; tripletIndex1++)
                        {
                            for (int tripletIndex2 = tripletIndex1 + 1; tripletIndex2 < curSegment.SegmentEndIndex; tripletIndex2++)
                            {
                                tripletSimilarityInSegment += Sim(tripletIndex1, tripletIndex2);
                            }
                        }// end for - the segment has been calculated
                    }
                    catch (Exception)
	{

        System.Console.WriteLine("InternalCohesion error");
	}


                    // NbCouples - The number of possible couples of triplets in seg
                    // endSegmentIndex - startTripletIndex1  = segment size
                    NbCouples = possibleNumCouplesOfTriplesInSegment(curSegment.SegmentSize);

                    // save internal similarity 
                    curSegment.InternalSimilarity = tripletSimilarityInSegment / NbCouples;

                    averageTripletSimilarityInSegment += (tripletSimilarityInSegment / NbCouples);

                    curSegment.IsChanged = false;
                } // is changed
                //else
                //{
                //    averageTripletSimilarityInSegment += curSegment.InternalSimilarity;
                //}

                startTripletIndex1 += m_codon.CodonNumNucleotides;
                // start calculate next segment similarity from zero
                tripletSimilarityInSegment = 0;



            } // end for = end all segments

            double internalCohesion = (double)(((double)1 / (double)(m_individualSegmentList.Count))) * (double)averageTripletSimilarityInSegment;

            return internalCohesion;
        }

      /// <summary>
      ///  | Segment Size  |  = (SegSize!) / (2! * (SegSize - 2)!)
      ///  |      2        |
      /// </summary>
      /// <param name="segmentSize"></param>
      /// <returns></returns>
        //private double possibleNumCouplesOfTriplesInSegment(int segmentSize)
        //{
        //   return fuctorial(segmentSize) / (2 * fuctorial(segmentSize - 2));
        //}

        private double possibleNumCouplesOfTriplesInSegment(int segmentSize)
        {
            return (double)(segmentSize * (segmentSize - 1)) / (double)2;
        }
        private long fuctorial(long num)
        {
            long numFactorial = 1;

            for (int i = 1; i <= num; i++)
            {
                numFactorial *= i;
            }
            return numFactorial;
        }

      /// <summary>
      ///  The similarity between segments is computed by the sum of triplet similarities 
      ///  between two segments dividing by total number of triplets of two comparing segments by follows way:
      /// </summary>
      /// <returns></returns>
        public double DissimilarityBetweenAdjacentSegments()
        {
            //System.Console.WriteLine("DissimilarityBetweenAdjacentSegments");

            double sumSimilaritySegments = 0;

            // if individual has only one segment, then there is no dissimilarity.
            if (m_individualSegmentList.Count < 2) return 0;

            for (int seg1Index = 0; seg1Index < m_individualSegmentList.Count - 1; seg1Index++)
            {
                Segment curSegment1 = m_individualSegmentList.ElementAt(seg1Index);
                Segment curSegment2 = m_individualSegmentList.ElementAt(seg1Index + 1);

                    // similarity of two segments
                  sumSimilaritySegments +=  SimSeg(curSegment1, curSegment2);
              //    Console.WriteLine("sum similarity: " + sumSimilaritySegments);
            } // end for = end all segments

            double Dx = 1 - (double)((double)sumSimilaritySegments / (double)(m_individualSegmentList.Count - 1));

            return Dx;
        }

        private double SimSeg(Segment curSegment1, Segment curSegment2)
        {
             // similarity of segments 
            double tripletSimilarityInSegments = 0;

            for (int tripletIndex1 = curSegment1.SegmentStartIndex; tripletIndex1 < curSegment1.SegmentEndIndex; tripletIndex1++)
            {
                for (int tripletIndex2 = curSegment2.SegmentStartIndex; tripletIndex2 < curSegment2.SegmentEndIndex; tripletIndex2++)
                {
                    tripletSimilarityInSegments += Sim(tripletIndex1, tripletIndex2);
                }
            }// end for - the segment has been calculated
            double segSimilarity = (double)((double)tripletSimilarityInSegments / (double)(curSegment1.SegmentSize * curSegment2.SegmentSize));
            return segSimilarity;
        }



        public enum NUCLEOTID { A = 0, T = 1, C = 2, G = 3 };
        internal void Optimize()
        {
            int[][] segmentDest = AllocDimensionArray(4, m_codon.CodonNumNucleotides);
            String archeTripletX = String.Empty;
            String archeTripletY= String.Empty;
            List<Segment> tempList = new List<Segment>();
            IndividualSegmentList.Sort();
            for (int i = 0; i < IndividualSegmentList.Count-1; i++)
            {
                Segment curSegment = IndividualSegmentList.ElementAt(i);
                for (int numCodonInSegmnt = 0; numCodonInSegmnt < curSegment.SegmentSize; numCodonInSegmnt++)
                {
                    String triplet = m_gene.Substring((curSegment.SegmentStartIndex + numCodonInSegmnt) * m_codon.CodonNumNucleotides, m_codon.CodonNumNucleotides);
                    AddTripletToSegment(segmentDest, triplet);
                }

                EvaluateCostFunction(segmentDest, curSegment.SegmentSize, ref archeTripletX); 
                ZeroSegNuclDistribution(segmentDest);
                
                Segment curSegment2 = IndividualSegmentList.ElementAt(i+1);
                for (int numCodonInSegmnt = 0; numCodonInSegmnt < curSegment2.SegmentSize; numCodonInSegmnt++)
                {
                    String triplet = m_gene.Substring((curSegment2.SegmentStartIndex + numCodonInSegmnt) * m_codon.CodonNumNucleotides, m_codon.CodonNumNucleotides);
                    AddTripletToSegment(segmentDest, triplet);
                }
                EvaluateCostFunction(segmentDest, curSegment.SegmentSize, ref archeTripletY);

                if (archeTripletX == archeTripletY)
                {
                    IndividualSegmentList.ElementAt(i + 1).SegmentStartIndex = IndividualSegmentList.ElementAt(i).SegmentStartIndex;
                    tempList.Add(IndividualSegmentList.ElementAt(i));
                }
               
                ZeroSegNuclDistribution(segmentDest);
               
            }
            foreach (Segment seg in tempList)
            {
                if(IndividualSegmentList.Contains(seg)==true)
                    IndividualSegmentList.Remove(seg);
            }
        }
        private void ZeroSegNuclDistribution(int[][] SegLeftNuclDistr)
        {
            for (int i = 0; i < SegLeftNuclDistr.Length; ++i)
                for (int j = 0; j < SegLeftNuclDistr[i].Length; j++)
                    SegLeftNuclDistr[i][j] = 0;
        }
        private int CnvtNucliotideToIndx(char nucliotide)
        {
            int index = 0;
            if (Enum.IsDefined(typeof(NUCLEOTID), Convert.ToString(nucliotide).ToUpper()))
                index = (int)Enum.Parse(typeof(NUCLEOTID), Convert.ToString(nucliotide), true);

            return index;
        }
        private void AddTripletToSegment(int[][] segmentDest, String triplet)
        {

            for (int index = 0; index < m_codon.CodonNumNucleotides; index++)
            {
                segmentDest[CnvtNucliotideToIndx(triplet[index])][index]++;
            }
        }
        private int[][] AllocDimensionArray(int rows, int columns)
        {
            int[][] dimensionArray = new int[rows][];
            for (int i = 0; i < rows; i++)
            {
                dimensionArray[i] = new int[columns];
            }
            return dimensionArray;
        }
        private double EvaluateCostFunction(int[][] SegmentOfTriplets, int segmentSize, ref String archeTriplet)
        {
            int cost = 0;
            int[] maxNuclDist = new int[m_codon.CodonNumNucleotides];
            int i = 0, j = 0;
            archeTriplet = String.Empty;
            String nucl = String.Empty;
            //triplet size is 3
            while (i < m_codon.CodonNumNucleotides)
            {
                j = 0;
                while (j < 4)
                {//4-number of nucleotides
                    if (maxNuclDist[i] < SegmentOfTriplets[j][i])
                    {
                        maxNuclDist[i] = SegmentOfTriplets[j][i];
                        nucl = ((NUCLEOTID)j).ToString();
                    }
                    j++;
                }
                cost += maxNuclDist[i];
                archeTriplet += nucl;
                i++;
            }
            return (double)cost / segmentSize;
        }
    }
}
